package com.tcoding.client.test.controlsfx;

import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.geometry.Pos;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableColumn;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.HBox;
import javafx.util.StringConverter;
import org.controlsfx.control.action.ActionUtils;
import org.controlsfx.control.tableview2.TableColumn2;
import org.controlsfx.control.tableview2.TableView2;
import org.controlsfx.control.tableview2.actions.ColumnFixAction;
import org.controlsfx.control.tableview2.actions.RowFixAction;
import org.controlsfx.control.tableview2.cell.ComboBox2TableCell;
import org.controlsfx.control.tableview2.cell.TextField2TableCell;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Arrays;

public class TableView2Sample extends TableView2<TableView2Sample.Person2> {

    private ObservableList<Person2> data;


    private final TableColumn2<Person2, String> firstName = new TableColumn2<>("First Name");
    private final TableColumn2<Person2, String> lastName = new TableColumn2<>("Last Name");
    private final TableColumn2<Person2, String> city = new TableColumn2<>("City");
    private final TableColumn2<Person2, Integer> age = new TableColumn2<>("Age");
    private final TableColumn2<Person2, LocalDate> birthday = new TableColumn2<>("Birthday");
    private final TableColumn2<Person2, Boolean> active = new TableColumn2<>("Active");
    private HBox boxFirstName, boxLastName;

    public TableView2Sample( ObservableList<Person2> items2) {
        super(items2);
        this.data = items2;
        setEditable(true);
        firstName.setCellValueFactory(p -> p.getValue().firstNameProperty());
        firstName.setCellFactory(ComboBox2TableCell.forTableColumn("Name 1", "Name 2", "Name 3", "Name 4"));
        firstName.setPrefWidth(110);

        lastName.setCellValueFactory(p -> p.getValue().lastNameProperty());
        lastName.setCellFactory(TextField2TableCell.forTableColumn());
        lastName.setPrefWidth(130);

        city.setCellValueFactory(p -> p.getValue().cityProperty());
        city.setCellFactory(TextField2TableCell.forTableColumn());
        city.setPrefWidth(90);

        age.setCellValueFactory((TableColumn.CellDataFeatures<Person2, Integer> p) -> p.getValue().ageProperty().asObject());
        age.setCellFactory(TextField2TableCell.forTableColumn(new StringConverter<Integer>() {
            @Override
            public String toString(Integer object) {
                return String.valueOf(object);
            }

            @Override
            public Integer fromString(String string) {
                return Integer.parseInt(string);
            }
        }));
        age.setPrefWidth(60);

        birthday.setCellValueFactory(p -> p.getValue().birthdayProperty());
        birthday.setPrefWidth(100);
        birthday.setCellFactory(TextFieldTableCell.forTableColumn(new StringConverter<LocalDate>() {
            @Override
            public String toString(LocalDate date) {
                if (date == null) {
                    return "" ;
                }
                return DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).format(date);
            }

            @Override
            public LocalDate fromString(String string) {
                return LocalDate.now();
            }

        }));

        active.setText("Active");
        active.setCellValueFactory(p -> p.getValue().activeProperty());
        active.setCellFactory(CheckBoxTableCell.forTableColumn(active));
        active.setPrefWidth(60);

        setItems(data);

        TableColumn fullNameColumn = new TableColumn("Full Name");
        fullNameColumn.getColumns().addAll(firstName, lastName);
        getColumns().setAll(fullNameColumn, city, age, birthday, active);

        ContextMenu cm = ActionUtils.createContextMenu(Arrays.asList(new ColumnFixAction(fullNameColumn)));
        fullNameColumn.setContextMenu(cm);

        Label labelFirstName = new Label("#1:");
        labelFirstName.textProperty().bind(Bindings.createStringBinding(() ->
                "#1: " + getItems().stream().filter(t -> t.getFirstName().contains("1")).count(), getItems()));
        boxFirstName = new HBox(10, labelFirstName);
        boxFirstName.setAlignment(Pos.CENTER);

        Label labelLastName = new Label("#1:");
        labelLastName.textProperty().bind(Bindings.createStringBinding(() ->
                "#1: " + getItems().stream().filter(t -> t.getLastName().contains("1")).count(), getItems()));
        boxLastName = new HBox(10, labelLastName);
        boxLastName.setAlignment(Pos.CENTER);

        cm = ActionUtils.createContextMenu(Arrays.asList(new ColumnFixAction(city)));
        city.setContextMenu(cm);

        cm = ActionUtils.createContextMenu(Arrays.asList(new ColumnFixAction(birthday), ActionUtils.ACTION_SEPARATOR));
        birthday.setContextMenu(cm);

        cm = ActionUtils.createContextMenu(Arrays.asList(new ColumnFixAction(active)));
        active.setContextMenu(cm);

        setRowHeaderContextMenuFactory((i, person) -> {
            ContextMenu rowCM = ActionUtils.createContextMenu(Arrays.asList(new RowFixAction(this, i), ActionUtils.ACTION_SEPARATOR));

            final MenuItem menuItem = new MenuItem("Remove  " + person.getFirstName());
            menuItem.setOnAction(e -> {
                if (i >= 0) {
                    final ObservableList<Person2> items = getItems();
                    if (items instanceof SortedList) {
                        int sourceIndex = ((SortedList<Person2>) items).getSourceIndexFor(data, i);
                        data.remove(sourceIndex);
                    } else {
                        data.remove(i.intValue());
                    }
                }
            });
            final MenuItem menuItemAdd = new MenuItem("Add new Person2");
            menuItemAdd.setOnAction(e -> data.add(new Person2()));
            rowCM.getItems().addAll(menuItem, menuItemAdd);
            return rowCM;
        });

        getFixedColumns().setAll(fullNameColumn);
        getFixedRows().setAll(0, 1, 2);
    }

    public void setupSouth(boolean useSouthNode) {
        if (useSouthNode) {
            firstName.setSouthNode(boxFirstName);
            lastName.setSouthNode(boxLastName);
        } else {
            firstName.setSouthNode(null);
            lastName.setSouthNode(null);
        }
    }

    static class Person2 {

        private final StringProperty firstName = new SimpleStringProperty();
        private final StringProperty lastName = new SimpleStringProperty();
        private final IntegerProperty age = new SimpleIntegerProperty();
        private final StringProperty city = new SimpleStringProperty();
        private final BooleanProperty active = new SimpleBooleanProperty();
        private final ObjectProperty<LocalDate> birthday = new SimpleObjectProperty<>();

        public final LocalDate getBirthday() {
            return birthday.get();
        }

        public final void setBirthday(LocalDate value) {
            birthday.set(value);
        }

        public final ObjectProperty<LocalDate> birthdayProperty() {
            return birthday;
        }


        public final StringProperty firstNameProperty() {
            return this.firstName;
        }

        public final java.lang.String getFirstName() {
            return this.firstNameProperty().get();
        }

        public final void setFirstName(final java.lang.String firstName) {
            this.firstNameProperty().set(firstName);
        }

        public final StringProperty lastNameProperty() {
            return this.lastName;
        }

        public final java.lang.String getLastName() {
            return this.lastNameProperty().get();
        }

        public final void setLastName(final java.lang.String lastName) {
            this.lastNameProperty().set(lastName);
        }

        public final StringProperty cityProperty() {
            return this.city;
        }

        public final java.lang.String getCity() {
            return this.cityProperty().get();
        }

        public final void setCity(final java.lang.String city) {
            this.cityProperty().set(city);
        }

        public final BooleanProperty activeProperty() {
            return this.active;
        }

        public final boolean isActive() {
            return this.activeProperty().get();
        }

        public final void setActive(final boolean active) {
            this.activeProperty().set(active);
        }

        public final int getAge() {
            return age.get();
        }

        public final IntegerProperty ageProperty() {
            return age;
        }

        public final void setAge(int age) {
            this.age.set(age);
        }

        public Person2 (String firstName, String lastName, Integer age,
                       String city, boolean active, LocalDate birthday){
            this.firstName.set(firstName);
            this.lastName.set(lastName);
            this.age.set(age);
            this.city.set(city);
            this.active.set(active);
            this.birthday.set(birthday);
        }

        @Override
        public String toString() {
            return "Person2{" + "firstName=" + firstName.get() + ", lastName=" + lastName.get() +
                    ", age=" + age.get() + ", city=" + city.get() + ", active=" + active.get() +
                    ", birthday=" + birthday.get() + '}';
        }


        public Person2() {
            this.firstName.set("");
            this.lastName.set("");
            this.age.set(18);
            this.city.set("");
            this.active.set(false);
            this.birthday.set(LocalDate.now());
        }

        public IntegerProperty getTotalSum() {
            IntegerProperty sum = new SimpleIntegerProperty();
            sum.bind(Bindings.createIntegerBinding(() -> getNumberOf(getFirstName()) + getNumberOf(getLastName()) + getNumberOf(getCity()),
                    firstName, lastName, city));
            return sum;
        }

        private int getNumberOf(String text) {
            try {
                final String[] split = text.split(":");
                if (split.length == 2) {
                    return Integer.parseInt(split[1].trim());
                }
            } catch (NumberFormatException e) {}
            return 0;
        }
    }
}
